package org.finesys.chat.core.base.embedding.store.redis;

import org.finesys.chat.core.base.embedding.filter.Filter;
import org.finesys.chat.core.base.embedding.filter.compairson.*;
import org.finesys.chat.core.base.embedding.filter.logical.AndFilter;
import org.finesys.chat.core.base.embedding.filter.logical.NotFilter;
import org.finesys.chat.core.base.embedding.filter.logical.OrFilter;
import redis.clients.jedis.search.schemafields.NumericField;
import redis.clients.jedis.search.schemafields.SchemaField;
import redis.clients.jedis.search.schemafields.TagField;
import redis.clients.jedis.search.schemafields.TextField;

import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;

public class RedisMetadataFilterMapper {

    private static final String FILTER_PREFIX = "@";
    private static final String NOT_PREFIX = "-";
    private static final String OR_DELIMITER = " | ";
    private final Map<String, SchemaField> schemaFieldMap;


    public RedisMetadataFilterMapper(Map<String, SchemaField> schemaFieldMap) {
        this.schemaFieldMap = schemaFieldMap;
    }


    public String map(Filter filter) {
        if (filter == null) {
            return "(*)";
        } else if (filter instanceof EqualFilter) {
            return this.mapEqualFilter((EqualFilter) filter);
        } else if (filter instanceof NotEqualFilter) {
            return this.mapNotEqualFilter((NotEqualFilter) filter);
        } else if (filter instanceof GreaterThanFilter) {
            return this.mapGreaterThanFilter((GreaterThanFilter) filter);
        } else if (filter instanceof GreaterThanEqualFilter) {
            return this.mapGreaterThanEqualFilter((GreaterThanEqualFilter) filter);
        } else if (filter instanceof LessThanEqualFilter) {
            return this.mapLessThanEqualFilter((LessThanEqualFilter) filter);
        } else if (filter instanceof LessThanFilter) {
            return this.mapLessThanFilter((LessThanFilter) filter);
        } else if (filter instanceof LikeFilter) {
            return this.mapLikeFilter((LikeFilter) filter);
        } else if (filter instanceof InFilter) {
            return this.mapInFilter((InFilter) filter);
        } else if (filter instanceof NotInFilter) {
            return this.mapNotInFilter((NotInFilter) filter);
        } else if (filter instanceof AndFilter) {
            return this.mapAndFilter((AndFilter) filter);
        } else if (filter instanceof OrFilter) {
            return this.mapOrFilter((OrFilter) filter);
        } else if (filter instanceof NotFilter) {
            return this.mapNotFilter((NotFilter) filter);
        } else {
            throw new RuntimeException("不支持的过滤器类型");
        }
    }


    private String mapEqualFilter(EqualFilter filter) {
        return this.doMapEqualFilter(filter.getKey(), filter.getCompairsonValue());
    }


    private String mapNotEqualFilter(NotEqualFilter filter) {
        return this.doMapNot(this.doMapEqualFilter(filter.getKey(), filter.getCompairsonValue()));
    }

    private String mapGreaterThanFilter(GreaterThanFilter filter) {
        Numeric numeric = RedisMetadataFilterMapper.Numeric.constructNumeric(filter.getCompairsonValue(), true);
        return this.doMapCompare(filter.getKey(), numeric.toString(), Numeric.POSITIVE_INFINITY.toString());
    }


    private String mapGreaterThanEqualFilter(GreaterThanEqualFilter filter) {
        Numeric value = RedisMetadataFilterMapper.Numeric.constructNumeric(filter.getCompairsonValue(), false);
        return this.doMapCompare(filter.getKey(), value.toString(), RedisMetadataFilterMapper.Numeric.POSITIVE_INFINITY.toString());
    }

    private String mapInFilter(InFilter filter) {
        return this.doMapIn(filter.getKey(), filter.getComparisonValues());
    }

    private String mapNotInFilter(NotInFilter filter) {
        return this.doMapNot(this.doMapIn(filter.getKey(), filter.getComparisonValues()));
    }

    private String mapLessThanEqualFilter(LessThanEqualFilter filter) {
        Numeric numeric = RedisMetadataFilterMapper.Numeric.constructNumeric(filter.getCompairsonValue(), false);
        return this.doMapCompare(filter.getKey(), Numeric.NEGATIVE_INFINITY.toString(), numeric.toString());
    }

    private String mapLessThanFilter(LessThanFilter filter) {
        Numeric numeric = RedisMetadataFilterMapper.Numeric.constructNumeric(filter.getCompairsonValue(), true);
        return this.doMapCompare(filter.getKey(), Numeric.NEGATIVE_INFINITY.toString(), numeric.toString());
    }

    /**
     * 将 LikeFilter 转换为数据库查询字符串
     * redis 5.0以上才支持
     *
     * @param filter LikeFilter 对象
     * @return 转换后的查询字符串
     */
    private String mapLikeFilter(LikeFilter filter) {
        String key = filter.getKey();
        SchemaField schemaField = (SchemaField) this.schemaFieldMap.getOrDefault(key, TagField.of(key));
        String fieldName = schemaField.getName();
        return String.format("@%s:(*%s*)", fieldName, filter.getCompairsonValue());
    }

    private String mapAndFilter(AndFilter filter) {
        return String.format(" (%s %s)", this.map(filter.getLeft()), this.map(filter.getRight()));
    }

    private String mapOrFilter(OrFilter filter) {
        return String.format(" (%s %s %s)", this.map(filter.getLeft()), OR_DELIMITER, this.map(filter.getRight()));
    }

    private String mapNotFilter(NotFilter filter) {
        return this.doMapNot(this.map(filter.getExpression()));
    }


    /**
     * 将给定的键转换成带有前缀的键
     *
     * @param key 待转换的键
     * @return 带有前缀的键
     */
    private String toPrefixKey(String key) {
        return String.format("%s%s:", FILTER_PREFIX, key);
    }


    /**
     * 根据key和compairsonValue进行map相等过滤操作
     *
     * @param key             key值
     * @param compairsonValue 要比较的值
     * @return 过滤后的字符串
     * @throws UnsupportedOperationException 如果schemaField的类型不被支持，则抛出此异常
     */
    private String doMapEqualFilter(String key, Object compairsonValue) {
        SchemaField schemaField = (SchemaField) this.schemaFieldMap.getOrDefault(key, TagField.of(key));
        String keyPrefix = this.toPrefixKey(key);
        if (schemaField instanceof NumericField) {
            return keyPrefix + RedisMetadataFilterMapper.Boundary.NUMERIC_BOUNDARY.toSingleString(compairsonValue);
        }
        if (schemaField instanceof TagField) {
            return keyPrefix + RedisMetadataFilterMapper.Boundary.TAG_BOUNDARY.toSingleString(compairsonValue);
        }
        if (schemaField instanceof TextField) {
            return keyPrefix + RedisMetadataFilterMapper.Boundary.TEXT_BOUNDARY.toSingleString(compairsonValue);
        }
        throw new UnsupportedOperationException("不支持的类型" + String.valueOf(schemaField));
    }


    /**
     * 将传入的字符串前加上负号"-"
     *
     * @param filter 需要加上负号的字符串
     * @return 加上负号的字符串
     */
    private String doMapNot(String filter) {
        return String.format("(%s%s)", NOT_PREFIX, filter);
    }

    /**
     * 执行映射比较操作
     *
     * @param key        待比较的键
     * @param leftValue  左值
     * @param rightValue 右值
     * @return 比较后的结果字符串
     * @throws UnsupportedOperationException 如果字段类型不是数字类型，则抛出异常
     */
    private String doMapCompare(String key, String leftValue, String rightValue) {
        SchemaField schemaField = (SchemaField) this.schemaFieldMap.getOrDefault(key, TagField.of(key));
        if (schemaField instanceof NumericField) {
            String keyPrefix = this.toPrefixKey(key);
            return keyPrefix + RedisMetadataFilterMapper.Boundary.NUMERIC_BOUNDARY.toRangeString(leftValue, rightValue);
        } else {
            throw new UnsupportedOperationException("Redis do not support non-Numeric range search, fieldType: " + String.valueOf(schemaField));
        }
    }


    private String doMapIn(String key, Collection<?> values) {
        SchemaField schemaField = (SchemaField) this.schemaFieldMap.getOrDefault(key, TagField.of(key));
        String keyPrefix = this.toPrefixKey(key);
        String inFilter = "";
        if (schemaField instanceof TagField) {
            inFilter = (String) values.stream().map(Object::toString).collect(Collectors.joining(" | "));
            return keyPrefix + RedisMetadataFilterMapper.Boundary.TAG_BOUNDARY.toSingleString(inFilter);
        } else if (schemaField instanceof TextField) {
            inFilter = (String) values.stream().map(Object::toString).collect(Collectors.joining(" | "));
            return keyPrefix + RedisMetadataFilterMapper.Boundary.TEXT_IN_BOUNDARY.toSingleString(inFilter);
        } else {
            throw new UnsupportedOperationException("Redis do no support NumericType in search,fieldType" + String.valueOf(schemaField));
        }
    }


    static class Numeric {
        static final Numeric POSITIVE_INFINITY = new Numeric(Double.POSITIVE_INFINITY, true);
        static final Numeric NEGATIVE_INFINITY = new Numeric(Double.NEGATIVE_INFINITY, true);
        private static final String INFINITY = "inf";
        private static final String MINUS_INFINITY = "-inf";
        private static final String INCLUSIVE_FORMAT = "%s";
        private static final String EXCLUSIVE_FORMAT = "(%s";
        private final Object value;
        private final boolean exclusive;

        public Numeric(Object value, boolean exclusive) {
            this.value = value;
            this.exclusive = exclusive;
        }

        static Numeric constructNumeric(Object value, boolean exclusive) {
            return new Numeric(value, exclusive);
        }

        public String toString() {
            if (this == POSITIVE_INFINITY) {
                return INFINITY;
            }
            if (this == NEGATIVE_INFINITY) {
                return MINUS_INFINITY;
            }
            return String.format(this.formatString(), this.value);
        }

        private String formatString() {
            return this.exclusive ? EXCLUSIVE_FORMAT : INCLUSIVE_FORMAT;
        }
    }

    static class Boundary {

        private static final Boundary NUMERIC_BOUNDARY = new Boundary("[", "]");
        private static final Boundary TEXT_BOUNDARY = new Boundary("\"", "\"");
        private static final Boundary TEXT_IN_BOUNDARY = new Boundary("(", ")");
        private static final Boundary TAG_BOUNDARY = new Boundary("{", "}");

        private String left;
        private String right;

        public Boundary(String left, String right) {
            this.left = left;
            this.right = right;
        }

        public String toSingleString(Object value) {
            return String.format("%s%s%s", this.left, value, this.right);
        }

        public String toRangeString(Object leftValue, Object rightValue) {
            return String.format("%s%s %s%s", this.left, leftValue, rightValue, this.right);
        }
    }
}
